﻿// <copyright file="Primitive.cs" company="Joe Chung">
// Copyright © 2008 by Joe Chung.
// </copyright>
namespace NScheme
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Reflection;
    using System.Text;

    public sealed class Primitive : NScheme.Procedure
    {
        public const int EQ = 0;
        public const int LT = 1;
        public const int GT = 2;
        public const int GE = 3;
        public const int LE = 4;
        public const int ABS = 5;
        public const int EOF_OBJECT = 6;
        public const int EQQ = 7;
        public const int EQUALQ = 8;
        public const int FORCE = 9;
        public const int CAR = 10;
        public const int FLOOR = 11;
        public const int CEILING = 12;
        public const int CONS = 13;
        public const int DIVIDE = 14;
        public const int LENGTH = 15;
        public const int LIST = 16;
        public const int LISTQ = 17;
        public const int APPLY = 18;
        public const int MAX = 19;
        public const int MIN = 20;
        public const int MINUS = 21;
        public const int NEWLINE = 22;
        public const int NOT = 23;
        public const int NULLQ = 24;
        public const int NUMBERQ = 25;
        public const int PAIRQ = 26;
        public const int PLUS = 27;
        public const int PROCEDUREQ = 28;
        public const int READ = 29;
        public const int CDR = 30;
        public const int ROUND = 31;
        public const int SECOND = 32;
        public const int SYMBOLQ = 33;
        public const int TIMES = 34;
        public const int TRUNCATE = 35;
        public const int WRITE = 36;
        public const int APPEND = 37;
        public const int BOOLEANQ = 38;
        public const int SQRT = 39;
        public const int EXPT = 40;
        public const int REVERSE = 41;
        public const int ASSOC = 42;
        public const int ASSQ = 43;
        public const int ASSV = 44;
        public const int MEMBER = 45;
        public const int MEMQ = 46;
        public const int MEMV = 47;
        public const int EQVQ = 48;
        public const int LISTREF = 49;
        public const int LISTTAIL = 50;
        public const int STRINQ = 51;
        public const int MAKESTRING = 52;
        public const int STRING = 53;
        public const int STRINGLENGTH = 54;
        public const int STRINGREF = 55;
        public const int STRINGSET = 56;
        public const int SUBSTRING = 57;
        public const int STRINGAPPEND = 58;
        public const int STRINGTOLIST = 59;
        public const int LISTTOSTRING = 60;
        public const int SYMBOLTOSTRING = 61;
        public const int STRINGTOSYMBOL = 62;
        public const int EXP = 63;
        public const int LOG = 64;
        public const int SIN = 65;
        public const int COS = 66;
        public const int TAN = 67;
        public const int ACOS = 68;
        public const int ASIN = 69;
        public const int ATAN = 70;
        public const int NUMBERTOSTRING = 71;
        public const int STRINGTONUMBER = 72;
        public const int CHARQ = 73;
        public const int CHARALPHABETICQ = 74;
        public const int CHARNUMERICQ = 75;
        public const int CHARWHITESPACEQ = 76;
        public const int CHARUPPERCASEQ = 77;
        public const int CHARLOWERCASEQ = 78;
        public const int CHARTOINTEGER = 79;
        public const int INTEGERTOCHAR = 80;
        public const int CHARUPCASE = 81;
        public const int CHARDOWNCASE = 82;
        public const int STRINGQ = 83;
        public const int VECTORQ = 84;
        public const int MAKEVECTOR = 85;
        public const int VECTOR = 86;
        public const int VECTORLENGTH = 87;
        public const int VECTORREF = 88;
        public const int VECTORSET = 89;
        public const int LISTTOVECTOR = 90;
        public const int MAP = 91;
        public const int FOREACH = 92;
        public const int CALLCC = 93;
        public const int VECTORTOLIST = 94;
        public const int LOAD = 95;
        public const int DISPLAY = 96;
        public const int INPUTPORTQ = 98;
        public const int CURRENTINPUTPORT = 99;
        public const int OPENINPUTFILE = 100;
        public const int CLOSEINPUTPORT = 101;
        public const int OUTPUTPORTQ = 103;
        public const int CURRENTOUTPUTPORT = 104;
        public const int OPENOUTPUTFILE = 105;
        public const int CLOSEOUTPUTPORT = 106;
        public const int READCHAR = 107;
        public const int PEEKCHAR = 108;
        public const int EVAL = 109;
        public const int QUOTIENT = 110;
        public const int REMAINDER = 111;
        public const int MODULO = 112;
        public const int THIRD = 113;
        public const int EOFOBJECTQ = 114;
        public const int GCD = 115;
        public const int LCM = 116;
        public const int CXR = 117;
        public const int ODDQ = 118;
        public const int EVENQ = 119;
        public const int ZEROQ = 120;
        public const int POSITIVEQ = 121;
        public const int NEGATIVEQ = 122;
        public const int CHARCMP = 123;
        public const int CHARCICMP = 128;
        public const int STRINGCMP = 133;
        public const int STRINGCICMP = 138;
        public const int EXACTQ = 143;
        public const int INEXACTQ = 144;
        public const int INTEGERQ = 145;
        public const int CALLWITHINPUTFILE = 146;
        public const int CALLWITHOUTPUTFILE = 147;

        public const int NEW = -1;
        public const int CLASS = -2;
        public const int METHOD = -3;
        public const int EXIT = -4;
        public const int SETCAR = -5;
        public const int SETCDR = -6;
        public const int TIMECALL = -11;
        public const int MACROEXPAND = -12;
        public const int ERROR = -13;
        public const int LISTSTAR = -14;

        public int minArgs;

        public int maxArgs;

        public int idNumber;

        public Primitive(int id, int minArgs, int maxArgs)
        {
            this.idNumber = id;
            this.minArgs = minArgs;
            this.maxArgs = maxArgs;
        }

        public static object append(object args)
        {
            if (rest(args) == null)
            {
                return first(args);
            }
            else
            {
                return append2(first(args), append(rest(args)));
            }
        }

        public static object append2(object x, object y)
        {
            return x is Pair ? cons(first(x), append2(rest(x), y)) : y;
        }

        public static int charCompare(object x, object y, bool ci)
        {
            char xc = chr(x);
            char yc = chr(y);
            if (ci)
            {
                xc = char.ToLower(xc);
                yc = char.ToLower(yc);
            }

            return xc - yc;
        }

        public static object gcd(object args)
        {
            long gcd = 0;
            while (args is Pair)
            {
                gcd = gcd2(Math.Abs((long)num(first(args))), gcd);
                args = rest(args);
            }

            return num(gcd);
        }

        public static long gcd2(long a, long b)
        {
            if (b == 0)
            {
                return a;
            }
            else
            {
                return gcd2(b, a % b);
            }
        }

        public static Environment installPrimitives(Environment env)
        {
            int n = int.MaxValue;

            env
                .defPrim("=", EQUALQ, 2, n)
                .defPrim("*", TIMES, 0, n)
                .defPrim("+", PLUS, 0, n)
                .defPrim("-", MINUS, 1, n)
                .defPrim("/", DIVIDE, 1, n)
                .defPrim("<", LT, 2, n)
                .defPrim(">", GT, 2, n)
                .defPrim("<=", LE, 2, n)
                .defPrim(">=", GE, 2, n)
                .defPrim("abs", ABS, 1)
                .defPrim("acos", ACOS, 1)
                .defPrim("append", APPEND, 0, n)
                .defPrim("apply", APPLY, 2, n)
                .defPrim("asin", ASIN, 1)
                .defPrim("assoc", ASSOC, 2)
                .defPrim("assq", ASSQ, 2)
                .defPrim("assv", ASSV, 2)
                .defPrim("atan", ATAN, 1)
                .defPrim("boolean?", BOOLEANQ, 1)
                .defPrim("caaaar", CXR, 1)
                .defPrim("caaadr", CXR, 1)
                .defPrim("caaar", CXR, 1)
                .defPrim("caadar", CXR, 1)
                .defPrim("caaddr", CXR, 1)
                .defPrim("caar", CXR, 1)
                .defPrim("cadaar", CXR, 1)
                .defPrim("cadadr", CXR, 1)
                .defPrim("cadar", CXR, 1)
                .defPrim("caddar", CXR, 1)
                .defPrim("cadddr", CXR, 1)
                .defPrim("caddr", CXR, 1)
                .defPrim("cadr", CXR, 1)
                .defPrim("call-with-current-continuation", CALLCC, 1)
                .defPrim("call-with-input-file", CALLWITHINPUTFILE, 2)
                .defPrim("call-with-output-file", CALLWITHOUTPUTFILE, 2)
                .defPrim("car", CAR, 1)
                .defPrim("cdaaar,", CXR, 1)
                .defPrim("cdaadr", CXR, 1)
                .defPrim("cdaar", CXR, 1)
                .defPrim("cdadar", CXR, 1)
                .defPrim("cdaddr", CXR, 1)
                .defPrim("cdadr", CXR, 1)
                .defPrim("cdar", CXR, 1)
                .defPrim("cddaar", CXR, 1)
                .defPrim("cddadr", CXR, 1)
                .defPrim("cddar", CXR, 1)
                .defPrim("cdddar", CXR, 1)
                .defPrim("cddddr", CXR, 1)
                .defPrim("cdddr", CXR, 1)
                .defPrim("cddr", CXR, 1)
                .defPrim("cdr", CDR, 1)
                .defPrim("char->integer", CHARTOINTEGER, 1)
                .defPrim("char-alphabetic?", CHARALPHABETICQ, 1)
                .defPrim("char-ci<=?", CHARCICMP + LE, 2)
                .defPrim("char-ci<?", CHARCICMP + LT, 2)
                .defPrim("char-ci=?", CHARCICMP + EQ, 2)
                .defPrim("char-ci>=?", CHARCICMP + GE, 2)
                .defPrim("char-ci>?", CHARCICMP + GE, 2)
                .defPrim("char-downcase", CHARDOWNCASE, 1)
                .defPrim("char-lower-case?", CHARLOWERCASEQ, 1)
                .defPrim("char-numeric?", CHARNUMERICQ, 1)
                .defPrim("char-upcase", CHARUPCASE, 1)
                .defPrim("char-upper-case?", CHARUPPERCASEQ, 1)
                .defPrim("char-whitespace?", CHARWHITESPACEQ, 1)
                .defPrim("char<=?", CHARCMP + LE, 2)
                .defPrim("char<?", CHARCMP + LT, 2)
                .defPrim("char=?", CHARCMP + EQ, 2)
                .defPrim("char>=?", CHARCMP + GE, 2)
                .defPrim("char>?", CHARCMP + GT, 2)
                .defPrim("char?", CHARQ, 1)
                .defPrim("close-input-port", CLOSEINPUTPORT, 1)
                .defPrim("close-output-port", CLOSEOUTPUTPORT, 1)
                .defPrim("complex", NUMBERQ, 1)
                .defPrim("cons", CONS, 2)
                .defPrim("cos", COS, 1)
                .defPrim("current-input-port", CURRENTINPUTPORT, 0)
                .defPrim("current-output-port", CURRENTOUTPUTPORT, 0)
                .defPrim("display", DISPLAY, 1, 2)
                .defPrim("eof-object?", EOFOBJECTQ, 1)
                .defPrim("eq?", EQQ, 2)
                .defPrim("equal?", EQUALQ, 2)
                .defPrim("eqv?", EQVQ, 2)
                .defPrim("eval", EVAL, 1, 2)
                .defPrim("even?", EVENQ, 1)
                .defPrim("exact?", INTEGERQ, 1)
                .defPrim("exp", EXP, 1)
                .defPrim("expt", EXPT, 2)
                .defPrim("force", FORCE, 1)
                .defPrim("for-each", FOREACH, 1, n)
                .defPrim("gcd", GCD, 0, n)
                .defPrim("inexact?", INEXACTQ, 1)
                .defPrim("input-port?", INPUTPORTQ, 1)
                .defPrim("integer->char", INTEGERTOCHAR, 1)
                .defPrim("integer?", INTEGERQ, 1)
                .defPrim("lcm", LCM, 0, n)
                .defPrim("length", LENGTH, 1)
                .defPrim("list", LIST, 0, n)
                .defPrim("list->string", LISTTOSTRING, 1)
                .defPrim("list->vector", LISTTOVECTOR, 1)
                .defPrim("list-ref", LISTREF, 2)
                .defPrim("list-tail", LISTTAIL, 2)
                .defPrim("list?", LISTQ, 1)
                .defPrim("load", LOAD, 1)
                .defPrim("log", LOG, 1)
                .defPrim("macro-expand", MACROEXPAND, 1)
                .defPrim("make-string", MAKESTRING, 1, 2)
                .defPrim("make-vector", MAKEVECTOR, 1, 2)
                .defPrim("map", MAP, 1, n)
                .defPrim("max", MAX, 1, n)
                .defPrim("member", MEMBER, 2)
                .defPrim("memq", MEMQ, 2)
                .defPrim("memv", MEMV, 2)
                .defPrim("min", MIN, 1, n)
                .defPrim("modulo", MODULO, 2)
                .defPrim("negative?", NEGATIVEQ, 1)
                .defPrim("newline", NEWLINE, 0, 1)
                .defPrim("not", NOT, 1)
                .defPrim("null?", NULLQ, 1)
                .defPrim("number->string", NUMBERTOSTRING, 1, 2)
                .defPrim("number?", NUMBERQ, 1)
                .defPrim("odd?", ODDQ, 1)
                .defPrim("open-input-file", OPENINPUTFILE, 1)
                .defPrim("open-output-file", OPENOUTPUTFILE, 1)
                .defPrim("output-port?", OUTPUTPORTQ, 1)
                .defPrim("pair?", PAIRQ, 1)
                .defPrim("peek-char", PEEKCHAR, 0, 1)
                .defPrim("positive?", POSITIVEQ, 1)
                .defPrim("procedure?", PROCEDUREQ, 1)
                .defPrim("quotient", QUOTIENT, 2)
                .defPrim("rational?", INTEGERQ, 1)
                .defPrim("read", READ, 0, 1)
                .defPrim("read-char", READCHAR, 0, 1)
                .defPrim("real?", INTEGERQ, 1)
                .defPrim("remainder", REMAINDER, 2)
                .defPrim("reverse", REVERSE, 1)
                .defPrim("round", ROUND, 1)
                .defPrim("set-car!", SETCAR, 2)
                .defPrim("set-cdr!", SETCDR, 2)
                .defPrim("sin", SIN, 1)
                .defPrim("sqrt", SQRT, 1)
                .defPrim("string", STRING, 0, n)
                .defPrim("string->list", STRINGTOLIST, 1)
                .defPrim("string->number", STRINGTONUMBER, 1, 2)
                .defPrim("string->symbol", STRINGTOSYMBOL, 1)
                .defPrim("string-append", STRINGAPPEND, 0, n)
                .defPrim("string-ci<=?", STRINGCICMP + LE, 2)
                .defPrim("string-ci<?", STRINGCICMP + LT, 2)
                .defPrim("string-ci=?", STRINGCICMP + EQ, 2)
                .defPrim("string-ci>=?", STRINGCICMP + GE, 2)
                .defPrim("string-ci>?", STRINGCICMP + GT, 2)
                .defPrim("string-length", STRINGLENGTH, 1)
                .defPrim("string-ref", STRINGREF, 2)
                .defPrim("string-set!", STRINGSET, 3)
                .defPrim("string<=?", STRINGCMP + LE, 2)
                .defPrim("string<?", STRINGCMP + LT, 2)
                .defPrim("string=?", STRINGCMP + EQ, 2)
                .defPrim("string>=?", STRINGCMP + GE, 2)
                .defPrim("string>?", STRINGCMP + GT, 2)
                .defPrim("string?", STRINGQ, 1)
                .defPrim("substring", SUBSTRING, 3)
                .defPrim("symbol->string", SYMBOLTOSTRING, 1)
                .defPrim("symbol?", SYMBOLQ, 1)
                .defPrim("tan", TAN, 1)
                .defPrim("vector", VECTOR, 0, n)
                .defPrim("vector->list", VECTORTOLIST, 1)
                .defPrim("vector->length", VECTORLENGTH, 1)
                .defPrim("vector-ref", VECTORREF, 2)
                .defPrim("vector-set!", VECTORSET, 3)
                .defPrim("vector?", VECTORQ, 1)
                .defPrim("write", WRITE, 1, 2)
                .defPrim("write-char", DISPLAY, 1, 2)
                .defPrim("zero?", ZEROQ, 1)

                // EXTENSIONS
                .defPrim("new", NEW, 1)
                .defPrim("class", CLASS, 1)
                .defPrim("method", METHOD, 2, n)
                .defPrim("exit", EXIT, 0, 1)
                .defPrim("error", ERROR, 0, n)
                .defPrim("time-call", TIMECALL, 1, 2)
                .defPrim("_list*", LISTSTAR, 0, n)
                ;

            return env;
        }

        public static bool isExact(object x)
        {
            if (!(x is double))
            {
                return false;
            }

            double d = num(x);
            return d == Math.Round(d) && Math.Abs(d) < 102962884861573423.0;
        }

        public static bool isList(object x)
        {
            object slow = x;
            object fast = x;
            while (true)
            {
                if (fast == null)
                {
                    return true;
                }

                if (slow == rest(fast) || !(fast is Pair) || !(slow is Pair))
                {
                    return false;
                }

                fast = rest(fast);
                if (fast == null)
                {
                    return true;
                }

                if (!(fast is Pair))
                {
                    return false;
                }

                fast = rest(fast);
            }
        }

        public static object lcm(object args)
        {
            long L = 1;
            long g = 1;
            while (args is Pair)
            {
                long n = Math.Abs((long)num(first(args)));
                g = gcd2(n, L);
                L = g == 0 ? g : (n / g) * L;
                args = rest(args);
            }

            return num(L);
        }

        public static Pair map(Procedure proc, object args, Scheme interp, Pair result)
        {
            Pair accum = result;
            if (rest(args) == null)
            {
                args = first(args);
                while (args is Pair)
                {
                    object x = proc.apply(interp, list(first(args)));
                    if (accum != null)
                    {
                        accum = (Pair)(accum.rest = list(x));
                    }

                    args = rest(args);
                }
            }
            else
            {
                Procedure car = Procedure.proc(interp.eval("car"));
                Procedure cdr = Procedure.proc(interp.eval("cdr"));
                while (first(args) is Pair)
                {
                    object x = proc.apply(interp, map(car, list(args), interp, list(null)));
                    if (accum != null)
                    {
                        accum = (Pair)(accum.rest = list(x));
                    }

                    args = map(cdr, list(args), interp, list(null));
                }
            }

            return (Pair)rest(result);
        }

        public static object memberAssoc(object obj, object list, char m, char eq)
        {
            while (list is Pair)
            {
                object target = m == 'm' ? first(list) : first(first(list));
                bool found;
                switch (eq)
                {
                    case 'q':
                        found = target == obj;
                        break;

                    case 'v':
                        found = eqv(target, obj);
                        break;

                    case ' ':
                        found = equal(target, obj);
                        break;

                    default:
                        warn("Bad option to memberAssoc: " + eq);
                        return FALSE;
                }

                if (found)
                {
                    return m == 'm' ? list : first(list);
                }

                list = rest(list);
            }

            return FALSE;
        }

        public static object numberToString(object x, object y)
        {
            int _base = y is double ? (int)num(y) : 10;
            if (_base != 10 || num(x) == Math.Round(num(x)))
            {
                return Convert.ToString((long)num(x), _base).ToCharArray();
            }
            else
            {
                return x.ToString().ToCharArray();
            }
        }

        public static object numCompare(object args, char op)
        {
            while (rest(args) is Pair)
            {
                double x = num(first(args));
                args = rest(args);

                double y = num(first(args));

                switch (op)
                {
                    case '>':
                        if (!(x > y))
                        {
                            return FALSE;
                        }

                        break;

                    case '<':
                        if (!(x < y))
                        {
                            return FALSE;
                        }

                        break;

                    case '=':
                        if (!(x == y))
                        {
                            return FALSE;
                        }

                        break;

                    case 'L':
                        if (!(x <= y))
                        {
                            return FALSE;
                        }

                        break;

                    case 'G':
                        if (!(x >= y))
                        {
                            return FALSE;
                        }

                        break;

                    default:
                        error("internal error: unrecognized op: " + op);
                        break;
                }
            }

            return TRUE;
        }

        public static object numCompute(object args, char op, double result)
        {
            if (args == null)
            {
                switch (op)
                {
                    case '-':
                        return num(0 - result);

                    case '/':
                        return num(1 / result);

                    default:
                        return num(result);
                }
            }
            else
            {
                while (args is Pair)
                {
                    double x = num(first(args));
                    args = rest(args);

                    switch (op)
                    {
                        case 'X':
                            if (x > result)
                            {
                                result = x;
                            }

                            break;

                        case 'N':
                            if (x < result)
                            {
                                result = x;
                            }

                            break;

                        case '+':
                            result += x;
                            break;

                        case '-':
                            result -= x;
                            break;

                        case '*':
                            result *= x;
                            break;

                        case '/':
                            result /= x;
                            break;

                        default:
                            error("internal error: unrecognized op: " + op);
                            break;
                    }
                }

                return num(result);
            }
        }

        public static InputPort openInputFile(object filename)
        {
            try
            {
                return new InputPort(new StreamReader(stringify(filename, false)));
            }
            catch (FileNotFoundException)
            {
                return (InputPort)error("No such file: " + stringify(filename));
            }
            catch (IOException ex)
            {
                return (InputPort)error("IOException: " + ex.Message);
            }
        }

        public static PrintWriter openOutputFile(object filename)
        {
            try
            {
                return new PrintWriter(new StreamWriter(stringify(filename, false)));
            }
            catch (FileNotFoundException)
            {
                return (PrintWriter)error("No such file: " + stringify(filename));
            }
            catch (IOException ex)
            {
                return (PrintWriter)error("IOException: " + ex.Message);
            }
        }

        public static int sign(int x)
        {
            return x > 0 ? +1 : (x < 0 ? -1 : 0);
        }

        public static char[] stringAppend(object args)
        {
            StringBuilder result = new StringBuilder();
            while (args is Pair)
            {
                result.Append(stringify(first(args), false));
                args = rest(args);
            }

            return result.ToString().ToCharArray();
        }

        public static int stringCompare(object x, object y, bool ci)
        {
            if (x is char[] && y is char[])
            {
                char[] xc = (char[])x;
                char[] yc = (char[])y;
                for (int i = 0; i < xc.Length; i++)
                {
                    int diff = !ci ? xc[i] - yc[i] : char.ToLower(xc[i]) - char.ToLower(yc[i]);
                    if (diff != 0)
                    {
                        return diff;
                    }
                }

                return xc.Length - yc.Length;
            }
            else
            {
                error("expected two strings, got: " + stringify(list(x, y)));
                return 0;
            }
        }

        public static object stringToNumber(object x, object y)
        {
            int _base = y is double ? (int)num(y) : 10;
            try
            {
                return _base == 10 ? double.Parse(stringify(x, false)) : num(Convert.ToInt64(stringify(x, false), _base));
            }
            catch (FormatException)
            {
                return FALSE;
            }
        }

        public override object apply(Scheme interp, object args)
        {
            int nArgs = length(args);
            if (nArgs < minArgs)
            {
                return error("too few args, " + nArgs + ", for " + this.name + ": " + args);
            }
            else if (nArgs > maxArgs)
            {
                return error("too many args, " + nArgs + ", for " + this.name + ": " + args);
            }

            object x = first(args);
            object y = second(args);

            switch (this.idNumber)
            {
                // 6.1 BOOLEANS
                case NOT:
                    return truth(x is bool && (bool)x == false);

                case BOOLEANQ:
                    return truth(x is bool);

                case EQVQ:
                    return truth(eqv(x, y));

                case EQQ:
                    return truth(x == y);

                case EQUALQ:
                    return truth(x.Equals(y));

                // 6.2 EQUIVALENCE PREDICATES
                case PAIRQ:
                    return truth(x is Pair);

                case LISTQ:
                    return truth(isList(x));

                case CXR:
                    for (int i = this.name.Length - 2; i >= 1; i--)
                    {
                        x = this.name[i] == 'a' ? first(x) : rest(x);
                    }

                    return x;

                case CONS:
                    return cons(x, y);

                case CAR:
                    return first(x);

                case CDR:
                    return rest(x);

                case SETCAR:
                    return setFirst(x, y);

                case SETCDR:
                    return setRest(x, y);

                case SECOND:
                    return second(x);

                case THIRD:
                    return third(x);

                case NULLQ:
                    return truth(x == null);

                case LIST:
                    return args;

                case LENGTH:
                    return num(length(x));

                case APPEND:
                    return args == null ? null : append(args);

                case REVERSE:
                    return reverse(x);

                case LISTTAIL:
                    for (int k = (int)num(y); k > 0; k--)
                    {
                        x = rest(x);
                    }

                    return x;

                case LISTREF:
                    for (int k = (int)num(y); k > 0; k--)
                    {
                        x = rest(x);
                    }

                    return first(x);

                case MEMQ:
                    return memberAssoc(x, y, 'm', 'q');

                case MEMV:
                    return memberAssoc(x, y, 'm', 'v');

                case MEMBER:
                    return memberAssoc(x, y, 'm', ' ');

                case ASSQ:
                    return memberAssoc(x, y, 'a', 'q');

                case ASSV:
                    return memberAssoc(x, y, 'a', 'v');

                case ASSOC:
                    return memberAssoc(x, y, 'a', ' ');

                // 6.4 SYMBOLS
                case SYMBOLQ:
                    return truth(x is string);

                case SYMBOLTOSTRING:
                    return SchemeUtils.sym(x).ToCharArray();

                case STRINGTOSYMBOL:
                    return string.Intern(new string(SchemeUtils.str(x)));

                // 6.5 NUMBERS
                case NUMBERQ:
                    return truth(x is byte || x is char || x is int || x is long || x is float || x is double || x is decimal);

                case ODDQ:
                    return truth(Math.Abs(num(x)) % 2 != 0);

                case EVENQ:
                    return truth(Math.Abs(num(x)) % 2 == 0);

                case ZEROQ:
                    return truth(num(x) == 0);

                case POSITIVEQ:
                    return truth(num(x) > 0);

                case NEGATIVEQ:
                    return truth(num(x) < 0);

                case INTEGERQ:
                    return truth(isExact(x));

                case INEXACTQ:
                    return truth(!isExact(x));

                case LT:
                    return numCompare(args, '<');

                case GT:
                    return numCompare(args, '>');

                case EQ:
                    return numCompare(args, '=');

                case LE:
                    return numCompare(args, 'L');

                case GE:
                    return numCompare(args, 'G');

                case MAX:
                    return numCompute(args, 'X', num(x));

                case MIN:
                    return numCompute(args, 'N', num(x));

                case PLUS:
                    return numCompute(args, '+', 0.0);

                case MINUS:
                    return numCompute(rest(args), '-', num(x));

                case TIMES:
                    return numCompute(args, '*', 1.0);

                case DIVIDE:
                    return numCompute(rest(args), '/', num(x));

                case QUOTIENT:
                    double d = num(x) / num(y);
                    return num(d > 0 ? Math.Floor(d) : Math.Ceiling(d));

                case REMAINDER:
                    return num((long)num(x) % (long)num(y));

                case MODULO:
                    long xi = (long)num(x);
                    long yi = (long)num(y);
                    long m = xi % yi;
                    return num(xi * yi > 0 || m == 0 ? m : m + yi);

                case ABS:
                    return num(Math.Abs(num(x)));

                case FLOOR:
                    return num(Math.Floor(num(x)));

                case CEILING:
                    return num(Math.Ceiling(num(x)));

                case TRUNCATE:
                    d = num(x);
                    return num(d < 0.0D ? Math.Ceiling(d) : Math.Floor(d));

                case ROUND:
                    return num(Math.Round(num(x)));

                case EXP:
                    return num(Math.Exp(num(x)));

                case LOG:
                    return num(Math.Log(num(x)));

                case SIN:
                    return num(Math.Sin(num(x)));

                case COS:
                    return num(Math.Cos(num(x)));

                case TAN:
                    return num(Math.Tan(num(x)));

                case ASIN:
                    return num(Math.Asin(num(x)));

                case ACOS:
                    return num(Math.Acos(num(x)));

                case ATAN:
                    return num(Math.Atan(num(x)));

                case SQRT:
                    return num(Math.Sqrt(num(x)));

                case EXPT:
                    return num(Math.Pow(num(x), num(y)));

                case NUMBERTOSTRING:
                    return numberToString(x, y);

                case STRINGTONUMBER:
                    return stringToNumber(x, y);

                case GCD:
                    return args == null ? ZERO : gcd(args);

                case LCM:
                    return args == null ? ONE : lcm(args);

                // 6.6 CHARACTERS
                case CHARQ:
                    return truth(x is char);

                case CHARALPHABETICQ:
                    return truth(char.IsLetter(chr(x)));

                case CHARNUMERICQ:
                    return truth(char.IsDigit(chr(x)));

                case CHARWHITESPACEQ:
                    return truth(char.IsWhiteSpace(chr(x)));

                case CHARUPPERCASEQ:
                    return truth(char.IsUpper(chr(x)));

                case CHARLOWERCASEQ:
                    return truth(char.IsLower(chr(x)));

                case CHARTOINTEGER:
                    return (double)chr(x);

                case INTEGERTOCHAR:
                    return chr((char)(int)num(x));

                case CHARUPCASE:
                    return chr(char.ToUpper(chr(x)));

                case CHARDOWNCASE:
                    return chr(char.ToLower(chr(x)));

                case CHARCMP + EQ:
                    return truth(charCompare(x, y, false) == 0);

                case CHARCMP + LT:
                    return truth(charCompare(x, y, false) < 0);

                case CHARCMP + GT:
                    return truth(charCompare(x, y, false) > 0);

                case CHARCMP + GE:
                    return truth(charCompare(x, y, false) >= 0);

                case CHARCMP + LE:
                    return truth(charCompare(x, y, false) <= 0);

                case CHARCICMP + EQ:
                    return truth(charCompare(x, y, true) == 0);

                case CHARCICMP + LT:
                    return truth(charCompare(x, y, true) < 0);

                case CHARCICMP + GT:
                    return truth(charCompare(x, y, true) > 0);

                case CHARCICMP + GE:
                    return truth(charCompare(x, y, true) >= 0);

                case CHARCICMP + LE:
                    return truth(charCompare(x, y, true) <= 0);

                case ERROR:
                    return error(stringify(args));

                // 6.7 STRINGS
                case STRINGQ:
                    return truth(x is char[]);

                case MAKESTRING:
                    char[] str = new char[(int)num(x)];
                    if (y != null)
                    {
                        char c = chr(y);
                        for (int i = str.Length - 1; i >= 0; i--)
                        {
                            str[i] = c;
                        }
                    }

                    return str;

                case STRING:
                    return listToString(args);

                case STRINGLENGTH:
                    return num(SchemeUtils.str(x).Length);

                case STRINGREF:
                    return chr(SchemeUtils.str(x)[(int)num(y)]);

                case STRINGSET:
                    object z = third(args);
                    SchemeUtils.str(x)[(int)num(y)] = chr(z);
                    return z;

                case SUBSTRING:
                    int start = (int)num(y);
                    int end = (int)num(third(args));
                    return SchemeUtils.str(x).ToString().Substring(start, end - start).ToCharArray();

                case STRINGAPPEND:
                    return stringAppend(args);

                case STRINGTOLIST:
                    Pair result = null;
                    char[] str2 = SchemeUtils.str(x);
                    for (int i = str2.Length - 1; i >= 0; i--)
                    {
                        result = cons(chr(str2[i]), result);
                    }

                    return result;

                case LISTTOSTRING:
                    return listToString(x);

                case STRINGCMP + EQ:
                    return truth(stringCompare(x, y, false) == 0);

                case STRINGCMP + LT:
                    return truth(stringCompare(x, y, false) < 0);

                case STRINGCMP + GT:
                    return truth(stringCompare(x, y, false) > 0);

                case STRINGCMP + GE:
                    return truth(stringCompare(x, y, false) >= 0);

                case STRINGCMP + LE:
                    return truth(stringCompare(x, y, false) <= 0);

                case STRINGCICMP + EQ:
                    return truth(stringCompare(x, y, true) == 0);

                case STRINGCICMP + LT:
                    return truth(stringCompare(x, y, true) < 0);

                case STRINGCICMP + GT:
                    return truth(stringCompare(x, y, true) > 0);

                case STRINGCICMP + GE:
                    return truth(stringCompare(x, y, true) >= 0);

                case STRINGCICMP + LE:
                    return truth(stringCompare(x, y, true) <= 0);

                // 6.8 VECTORS
                case VECTORQ:
                    return truth(x is object[]);

                case MAKEVECTOR:
                    object[] vec = new object[(int)num(x)];
                    if (y != null)
                    {
                        for (int i = 0; i < vec.Length; i++)
                        {
                            vec[i] = y;
                        }
                    }

                    return vec;

                case VECTOR:
                    return listToVector(args);

                case VECTORLENGTH:
                    return num(SchemeUtils.vec(x).Length);

                case VECTORREF:
                    return SchemeUtils.vec(x)[(int)num(y)];

                case VECTORSET:
                    return SchemeUtils.vec(x)[(int)num(y)] = third(args);

                case VECTORTOLIST:
                    return vectorToList(x);

                case LISTTOVECTOR:
                    return listToVector(x);

                // 6.9 CONTROL FEATURES
                case EVAL:
                    return interp.eval(x);

                case FORCE:
                    return !(x is Procedure) ? x : Procedure.proc(x).apply(interp, null);

                case MACROEXPAND:
                    return Macro.macroExpand(interp, x);

                case PROCEDUREQ:
                    return truth(x is Procedure);

                case APPLY:
                    return Procedure.proc(x).apply(interp, listStar(rest(args)));

                case MAP:
                    return map(Procedure.proc(x), rest(args), interp, list(null));

                case FOREACH:
                    return map(Procedure.proc(x), rest(args), interp, null);

                case CALLCC:
                    Exception cc = new Exception();
                    Continuation proc = new Continuation(cc);
                    try
                    {
                        return Procedure.proc(x).apply(interp, list(proc));
                    }
                    catch (Exception ex)
                    {
                        if (ex == cc)
                        {
                            return proc.value;
                        }
                        else
                        {
                            throw;
                        }
                    }

                // 6.10 INPUT AND OUTPUT
                case EOFOBJECTQ:
                    return truth(InputPort.isEOF(x));

                case INPUTPORTQ:
                    return truth(x is InputPort);

                case CURRENTINPUTPORT:
                    return interp.input;

                case OPENINPUTFILE:
                    return openInputFile(x);

                case CLOSEINPUTPORT:
                    return inPort(x, interp).close();

                case OUTPUTPORTQ:
                    return truth(x is PrintWriter);

                case CURRENTOUTPUTPORT:
                    return interp.output;

                case OPENOUTPUTFILE:
                    return openOutputFile(x);

                case CALLWITHOUTPUTFILE:
                    PrintWriter p = null;
                    try
                    {
                        p = openOutputFile(x);
                        z = Procedure.proc(y).apply(interp, list(p));
                    }
                    finally
                    {
                        if (p != null)
                        {
                            p.close();
                        }
                    }

                    return z;

                case CALLWITHINPUTFILE:
                    InputPort p2 = null;
                    try
                    {
                        p2 = openInputFile(x);
                        z = Procedure.proc(y).apply(interp, list(p2));
                    }
                    finally
                    {
                        if (p2 != null)
                        {
                            p2.close();
                        }
                    }

                    return z;

                case CLOSEOUTPUTPORT:
                    outPort(x, interp).close();
                    return TRUE;

                case READCHAR:
                    return inPort(x, interp).readChar();

                case PEEKCHAR:
                    return inPort(x, interp).peekChar();

                case LOAD:
                    return interp.load(x);

                case READ:
                    return inPort(x, interp).read();

                case EOF_OBJECT:
                    return truth(InputPort.isEOF(x));

                case WRITE:
                    return write(x, outPort(y, interp), true);

                case DISPLAY:
                    return write(x, outPort(y, interp), false);

                case NEWLINE:
                    outPort(x, interp).println();
                    outPort(x, interp).flush();
                    return TRUE;

                // EXTENSIONS
                case CLASS:
                    try
                    {
                        return Type.GetType(stringify(x, false));
                    }
                    catch (TypeLoadException)
                    {
                    }

                    return FALSE;

                case NEW:
                    try
                    {
                        Type type = ClrMethod.toClass(x);
                        Assembly assembly = type.Assembly;
                        return assembly.CreateInstance(type.FullName);
                    }
                    catch (ArgumentNullException)
                    {
                    }
                    catch (ArgumentException)
                    {
                    }
                    catch (BadImageFormatException)
                    {
                    }
                    catch (MissingMethodException)
                    {
                    }
                    catch (FileLoadException)
                    {
                    }
                    catch (FileNotFoundException)
                    {
                    }
                    catch (TargetInvocationException)
                    {
                    }

                    return FALSE;

                case METHOD:
                    return new ClrMethod(stringify(x, false), y, rest(rest(args)));

                case EXIT:
                    System.Environment.Exit(x == null ? 0 : (int)num(x));
                    return FALSE;

                case LISTSTAR:
                    return listStar(args);

                case TIMECALL:
                    long startMem = GC.GetTotalMemory(true);
                    Stopwatch stopwatch = Stopwatch.StartNew();
                    object ans = FALSE;
                    int nTimes = y == null ? 1 : (int)num(y);
                    for (int i = 0; i < nTimes; i++)
                    {
                        ans = Procedure.proc(x).apply(interp, null);
                    }

                    stopwatch.Stop();
                    long time = stopwatch.ElapsedMilliseconds;
                    long mem = GC.GetTotalMemory(false) - startMem;
                    return cons(ans, list(list(num(time), "msec"), list(num(mem), "bytes")));

                default:
                    return error("internal error: unknown primitive: " + this + " applied to " + args);
            }
        }
    }
}